#include <odbc/odbc_error.hpp>
#include <odbc/connection.hpp>
#include <odbc/environment.hpp>
#include <odbc/statement.hpp>
#include <vector>
#include <sstream>

namespace odbc
{

diagnostic_record::diagnostic_record( 
		const std::string& sqlState, 
		int nativeError, 
		const std::string& errorMessage)
	: sqlState_( sqlState),
	  nativeError_( nativeError),
	  errorMessage_( errorMessage)
{
}

// ---------------------------------------------------------------------------------------------------------------------

odbc_error::odbc_error( SQLRETURN resultErrorCode, const statement& stat)
	: resultErrorCode_( resultErrorCode),
	  std::runtime_error( "")
{
	this->fetch_diagnostics( stat.handle_, SQL_HANDLE_STMT);
	this->build_error_message();
}

// ---------------------------------------------------------------------------------------------------------------------

odbc_error::odbc_error( SQLRETURN resultErrorCode, const environment& env)
	: resultErrorCode_( resultErrorCode),
	  std::runtime_error( "")
{
	this->fetch_diagnostics( env.handle_, SQL_HANDLE_ENV);
	this->build_error_message();
}

// ---------------------------------------------------------------------------------------------------------------------

odbc_error::odbc_error( SQLRETURN resultErrorCode, const connection& conn)
	: resultErrorCode_( resultErrorCode),
		std::runtime_error( "")
{
	this->fetch_diagnostics( conn.handle_, SQL_HANDLE_DBC);
	this->build_error_message();
}

// ---------------------------------------------------------------------------------------------------------------------

/*static*/ void odbc_error::check_success( SQLRETURN resultCode, const statement& stat)
{
	if( resultCode != SQL_SUCCESS && resultCode != SQL_SUCCESS_WITH_INFO)
	{
		throw odbc_error( resultCode, stat);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

/*static*/ void odbc_error::check_success( SQLRETURN resultCode, const environment& env)
{
	if( resultCode != SQL_SUCCESS && resultCode != SQL_SUCCESS_WITH_INFO)
	{
		throw odbc_error( resultCode, env);
	}	
}

// ---------------------------------------------------------------------------------------------------------------------

/*static*/ void odbc_error::check_success( SQLRETURN resultCode, const connection& conn)
{
	if( resultCode != SQL_SUCCESS && resultCode != SQL_SUCCESS_WITH_INFO)
	{
		throw odbc_error( resultCode, conn);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void odbc_error::fetch_diagnostics( SQLHANDLE handle, SQLSMALLINT handleType)
{
	SQLCHAR sqlStat[6];
	SQLINTEGER nativeError;
	SQLSMALLINT recNo = 1;
	SQLRETURN retCode = SQL_SUCCESS;
	do
	{
		SQLSMALLINT messageLen;
		// obtain the message length
		retCode = SQLGetDiagRec( 
			handleType, 
			handle, 
			recNo, 
			sqlStat, 
			&nativeError, 
			NULL, 
			0, 
			&messageLen);
		if( retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO)
		{
			break;
		}
		std::vector< SQLCHAR> messageBuffer( messageLen + 1, 0);
		// get the error message and all the other stuff.
		retCode = SQLGetDiagRec( 
			handleType, 
			handle, 
			recNo, 
			sqlStat, 
			&nativeError, 
			static_cast< SQLCHAR*>( 
				&messageBuffer[0]), 
				SQLSMALLINT( messageBuffer.size()), 
			&messageLen);
		if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO)
		{
			// add the diagnostic record
			diagnostics_.push_back( 
				diagnostic_record( 
					std::string( sqlStat, sqlStat + 5), 
					int( nativeError), 
					std::string( messageBuffer.begin(), messageBuffer.end())));
		}
		++recNo;
	}
	while( retCode == SQL_SUCCESS);
}

// ---------------------------------------------------------------------------------------------------------------------

void odbc_error::build_error_message()
{
	std::ostringstream msgStrm;
	for( diag_rec_collection::iterator i = diagnostics_.begin();
		 i != diagnostics_.end();
		 ++i)
	{
		msgStrm << "Error " << i->native_error() << ": [" 
			<< i->sql_state() << "] " << i->error_message() << std::endl;
	}
	errorMessage_ = msgStrm.str();
}

// ---------------------------------------------------------------------------------------------------------------------

const char* odbc_error::what() const throw()
{
	return errorMessage_.c_str();
}

} // namespace odbc
